#include <drivers/i2c.h>
#include <kernel/bus.h>
#include <kernel/initcall.h>
#include <kernel/syslog.h>
#include <iomode.h>


#define  I2C_SM_START    0
#define  I2C_SM_ADDR     1
#define  I2C_SM_WRITE    2
#define  I2C_SM_READ     3
#define  I2C_SM_STOP     4


static inline void gpio_i2c_delay(size_t delay)
{
    for(volatile int i = 0; i < delay; i++);
}

static inline void gpio_i2c_start(struct i2c_adapter *adapter)
{
    gpio_i2c_delay(5);
    gpio_set_value(adapter->pin.sda, IO_LOW);   // set SDA low
    gpio_i2c_delay(5);
    gpio_set_value(adapter->pin.scl, IO_LOW);   // set SCL low
}


static inline void gpio_i2c_stop(struct i2c_adapter *adapter)
{
    gpio_i2c_delay(5);
    gpio_set_value(adapter->pin.scl, IO_HIGH);   // set SCL low
    gpio_i2c_delay(5);
    gpio_set_value(adapter->pin.sda, IO_HIGH);   // set SDA high
}


static inline int gpio_i2c_send_byte(struct i2c_adapter *adapter, uint8_t data)
{
    uint8_t mask = 0x80, ack = 0;

    for (int i = 0; i < 8; i++) {
        gpio_set_value(adapter->pin.sda, data & mask);
        gpio_i2c_delay(5);

        gpio_set_value(adapter->pin.scl, IO_HIGH);
        gpio_i2c_delay(5);
        gpio_set_value(adapter->pin.scl, IO_LOW);

        mask >>= 1;
    }

    // release SDA，wait slave to send ACK/NACK
    gpio_direction_input(adapter->pin.sda);

    gpio_i2c_delay(5);
    gpio_set_value(adapter->pin.scl, IO_HIGH);
    gpio_i2c_delay(5);

    ack = gpio_get_value(adapter->pin.sda);
    gpio_set_value(adapter->pin.scl, IO_LOW);
    gpio_direction_output(adapter->pin.sda, IO_LOW);

    //0: ack, 1: no ack, should to stop send data
    return ack;
}


uint8_t gpio_i2c_receive_byte(struct i2c_adapter *adapter, bool ack)
{
    uint8_t data = 0;

    gpio_direction_input(adapter->pin.sda);

    for (int i = 0; i < 8; i++) {
        gpio_i2c_delay(5);
        gpio_set_value(adapter->pin.scl, IO_HIGH);
        gpio_i2c_delay(5);

        data <<= 1;
        if (gpio_get_value(adapter->pin.sda)) {
            data |= 0x01;
        }
        gpio_set_value(adapter->pin.scl, IO_LOW);
    }

    gpio_direction_output(adapter->pin.sda, !ack);

    gpio_i2c_delay(5);
    gpio_set_value(adapter->pin.scl, IO_HIGH);
    gpio_i2c_delay(5);
    gpio_set_value(adapter->pin.scl, IO_LOW);

    return data;
}


int gpio_i2c_transfer(struct i2c_client *client, struct i2c_transfer *xfer, int num_xfer)
{
    struct i2c_adapter *adapter = client->adapter;
    int ret = 0, error = 0;

    for(int i = 0; i < num_xfer; i++) {
        uint8_t *tx_data = (uint8_t *)xfer->tx_buf;
        uint8_t *rx_data = (uint8_t *)xfer->rx_buf;
        int status = I2C_SM_START;

        for(int j = 0; j < xfer->len - 1; j++) {
            switch(status) {
                case I2C_SM_START:
                    gpio_i2c_start(adapter);
                    status = I2C_SM_ADDR;
                    break;

                case I2C_SM_ADDR:
                    if(xfer->flag == I2C_M_WRITE) {
                        ret = gpio_i2c_send_byte(adapter, (0x7F & client->addr) << 1);
                        if(ret) {
                            error = 1;
                            status = I2C_SM_STOP;
                        }
                        status = I2C_SM_WRITE;
                    }
                    if(xfer->flag == I2C_M_READ) {
                        ret = gpio_i2c_send_byte(adapter, (0x7F & client->addr) << 1 | 1);
                        if(ret) {
                            error = 1;
                            status = I2C_SM_STOP;
                        }
                        status = I2C_SM_READ;
                    }
                    break;

                case I2C_SM_WRITE:
                    ret = gpio_i2c_send_byte(adapter, *tx_data++);
                    if(ret) {
                        error = 2;
                        status = I2C_SM_STOP;
                    }
                    break;

                case I2C_SM_READ:
                    *rx_data = gpio_i2c_receive_byte(adapter, true);
                    rx_data ++;
                    break;

                case I2C_SM_STOP:
                    gpio_i2c_stop(adapter);
                    if(error == 1) {
                        I2C_SYSLOG_ERROR("send addr failed, no slave device ack");
                        return -1;
                    }
                    else if(error == 2) {
                        I2C_SYSLOG_ERROR("send data failed, slave device stop receive data");
                        return i + 1;
                    }
                    status = I2C_SM_START;
                    break;
            };
        }

        if(xfer->flag == I2C_M_WRITE) {
            gpio_i2c_send_byte(adapter, *tx_data);
        }
        else if(xfer->flag == I2C_M_READ) {
            *rx_data = gpio_i2c_receive_byte(adapter, false);
        }
        gpio_i2c_stop(adapter);
        status = I2C_SM_START;
    }

    return num_xfer;
}


static int gpio_i2c_driver_probe(struct platform_device *dev)
{
    fdt_node_t *i2c_node = dev->device.dt_node;

    size_t scl_pin, sda_pin;
    struct i2c_gpio pin;

    if(fdt_read_prop_int(i2c_node, "scl-gpio", &scl_pin)) {
        I2C_SYSLOG_ERROR("%s: not found scl pin", i2c_node->name);
        return -1;
    }
    if(gpio_request(scl_pin)) {
        I2C_SYSLOG_ERROR("%s: scl gpio request failed", i2c_node->name);
        return -1;
    }
    I2C_DEBUG_TRACE("%s: scl gpio(%d) request success", i2c_node->name, scl_pin);
    pin.scl = scl_pin;
    gpio_direction_output(scl_pin, IO_HIGH);

    if(fdt_read_prop_int(i2c_node, "sda-gpio", &sda_pin)) {
        I2C_SYSLOG_INFO("%s: not found sda pin", i2c_node->name);
        return -1;
    }
    if(gpio_request(sda_pin)) {
        I2C_SYSLOG_ERROR("%s: sda gpio request failed", i2c_node->name);
        return -1;
    }
    I2C_DEBUG_TRACE("%s: sda gpio(%d) request success", i2c_node->name, sda_pin);
    pin.sda = sda_pin;
    gpio_direction_output(sda_pin, IO_HIGH);

    struct i2c_adapter *adapter = kmalloc(sizeof(struct i2c_adapter));
    if(adapter == NULL) {
        I2C_SYSLOG_ERROR("alloc failed for i2c adapter");
    }

    adapter->io_base = dev->io_base;
    adapter->transfer = gpio_i2c_transfer;
    adapter->pin = pin;

    platform_set_platform_data(dev, adapter);

    fdt_node_t *child =  NULL;
    fdt_for_each_node_child(i2c_node, child) {
        struct i2c_client *i2c_client = kmalloc(sizeof(struct i2c_client));
        if(i2c_client == NULL) {
            I2C_SYSLOG_ERROR("alloc i2c client failed");
            return -1;
        }

        if(i2c_device_get_config_from_dt(child, i2c_client)) {
            kfree(i2c_client);
            return -1;
        }

        i2c_client->device.name = child->name;
        i2c_client->adapter = adapter;

        if(i2c_device_register(i2c_client)) {
            I2C_SYSLOG_ERROR("i2c device register failed");
            return -1;
        }
    }
    return 0;
}


static void gpio_i2c_driver_remove(struct platform_device *dev)
{

}


static struct of_device_id gpio_spi_driver_match[] = {
    {.compatible = "gpio-i2c", },
    { }
};


static struct platform_driver gpio_spi_pdrv = {
    .driver = {
        .name = "gpio-i2c",
        .of_match_table = gpio_spi_driver_match,
    },
    .probe = gpio_i2c_driver_probe,
    .remove = gpio_i2c_driver_remove,
};

static int gpio_i2c_driver_register(void)
{
    return platform_driver_register(&gpio_spi_pdrv);
}


driver_initcall(gpio_i2c_driver_register);
